#include <QNetworkRequest>
#include <QNetworkReply>
#include <QFile>
#include <QDebug>
#include <QMessageBox>
#include <QWaitCondition>
#include <QtConcurrentRun>
#include <QNetworkProxyFactory>

#include "updater.h"

Version::Version()
{
    license = Free;
    version = 1;
    build = 1;
}

void Version::read(const QDomElement & element)
{
    QString attribute = element.attribute("license");
    if(attribute == "free")
        license = Free;

    attribute = element.attribute("version");
    version = attribute.toInt();
    attribute = element.attribute("build");
    build = attribute.toInt();
}

void Version::write(QDomElement * element)
{
    element->setTagName("version");

    if(license == Free)
        element->setAttribute("license", "free");
    else
        element->setAttribute("license", "unknown");

    element->setAttribute("version", version);
    element->setAttribute("build", build);
}

bool Version::operator ==(const Version & ver) const
{
    return license == ver.license && version == ver.version && build == ver.build;
}

bool operator >(const Version & ver1, const Version & ver2)
{
    if(ver1.version > ver2.version)
        return true;

    if(ver1.version < ver2.version)
        return false;

    return ver1.build > ver2.build;
}

bool operator <(const Version & ver1, const Version & ver2)
{
    if(ver1.version < ver2.version)
        return true;

    if(ver1.version > ver2.version)
        return false;

    return ver1.build < ver2.build;
}

bool UpdateItem::read(const QDomElement & element)
{
    url = QUrl(element.attribute("url"));
    path = element.attribute("path");
    qDebug() << url.toString() << " " << path;

    return url.isValid() && !path.isNull();
}

bool Update::read(const QDomElement & element)
{
    version.read(element);
    QDomElement itemElement = element.firstChildElement();
    while(!itemElement.isNull())
    {
        UpdateItem item;
        if(!item.read(itemElement))
            return false;

        itemList.append(item);
        itemElement = itemElement.nextSiblingElement();
    }

    return true;
}

Updater::Updater(QObject * parent)
    : QObject(parent)
{
    readVersionInfo();
}

bool Updater::readVersionInfo()
{
    QFile file("version.xml");
    if(!file.open(QFile::ReadOnly))
        return false;

    QDomDocument doc;
    if(!doc.setContent(file.readAll()))
        return false;

    file.close();

    QDomElement versionElement = doc.firstChildElement();
    if(versionElement.isNull())
        return false;

    m_currectVersion.read(versionElement);
}

bool Updater::checkNeedUpdate()
{
    if(!downloadUpdatesInfo())
    {
        qDebug("Updates header is corrupted.");
        return false;
    }

    removeOldUpdates();

    if(m_updateList.isEmpty())
    {
        qDebug("Up to date");
        return false;
    }

    qDebug("Need to update");
    return true;
}

bool Updater::update()
{
    Version updatedVersion = m_updateList.last().version;

    createUpdateItemsList();

    connect(&m_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(downloadFinished(QNetworkReply*)));
    while(!m_updateItemList.isEmpty())
    {
        downloadUpdateItem(m_updateItemList.first());
        if(m_downloadEventLoop.exec() != 0)
        {
            qDebug() << "Failed to update" << m_updateItemList.first().path;
            return false;
        }

        m_updateItemList.removeFirst();
    }

    m_currectVersion = updatedVersion;
    updateVersionInfo();
    return true;
}

void Updater::updatesInfoDownloadFinished(QNetworkReply * reply)
{
    qDebug() << Q_FUNC_INFO;
    if(reply->error() != QNetworkReply::NoError)
    {
        qDebug() << "Failed to retrieve updates header. " << reply->errorString();
        m_downloadEventLoop.exit(1);
        return;
    }

    QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
    if(redirectUrl.isValid())
    {
        qDebug() << "Redirected to " << redirectUrl.toString();
        m_manager.get(QNetworkRequest(redirectUrl));
        return;
    }

    QString xml(reply->readAll());
    qDebug() << xml;
    QDomDocument doc;
    if(!doc.setContent(xml))
    {
        qDebug() << xml;
        m_downloadEventLoop.exit(1);
        return;
    }

    QDomElement rootElement = doc.firstChildElement();
    QDomElement updateElement = rootElement.firstChildElement();
    while(!updateElement.isNull())
    {
        Update update;
        if(!update.read(updateElement))
        {
            m_downloadEventLoop.exit(1);
            return;
        }

        m_updateList.append(update);
        updateElement = updateElement.nextSiblingElement();
    }

    m_downloadEventLoop.exit(0);
}

void Updater::downloadFinished(QNetworkReply * reply)
{
    if(reply->error() == QNetworkReply::NoError)
    {
        QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
        if(redirectUrl.isValid())
        {
            qDebug() << "Redirected to " << redirectUrl.toString();
            m_manager.get(QNetworkRequest(redirectUrl));
            return;
        }

        qDebug() << "Downloading " << reply->url().toString() << " finished.";
    }
    else
    {
        qDebug() << "Downloading " << reply->url().toString() << " failed with code " << reply->error() << "." << reply->errorString();
        m_downloadEventLoop.exit(1);
        return;
    }

    executeUpdateItem(reply, m_updateItemList.first());
    m_downloadEventLoop.exit(0);
}

bool Updater::downloadUpdatesInfo()
{
    QNetworkRequest request(QUrl("http://www.arsillustrator.com/updates.xml"));

    QList<QNetworkProxy> proxyList = QNetworkProxyFactory::systemProxyForQuery(request.url());
    if(!proxyList.isEmpty())
        m_manager.setProxy(proxyList.first());

    connect(&m_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(updatesInfoDownloadFinished(QNetworkReply*)));
    QNetworkReply * reply = m_manager.get(request);
    if(reply->error() != QNetworkReply::NoError)
    {
        qDebug() << "Cannot retrieve updates: " << reply->errorString();
        return false;
    }

    int downloadCode = m_downloadEventLoop.exec();
    disconnect(&m_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(updatesInfoDownloadFinished(QNetworkReply*)));

    return downloadCode == 0;
}

void Updater::removeOldUpdates()
{
    while(!m_updateList.isEmpty() && !(m_currectVersion < m_updateList.first().version))
        m_updateList.removeFirst();
}

QNetworkReply * Updater::downloadUpdateItem(const UpdateItem & item)
{
    qDebug() << "Downloading " << item.url.toString();
    QNetworkRequest request(item.url);
    QNetworkReply * reply = m_manager.get(request);
    return reply;
}

bool Updater::executeUpdateItem(QNetworkReply * reply, const UpdateItem & item)
{
    qDebug() << "Applying " << reply->url().toString() << " to " << item.path;
    QFile file(item.path);
    if(!file.open(QFile::WriteOnly))
    {
        qDebug() << "Cannot apply updates for " << file.fileName() << ". Access denied.";
        return false;
    }

    file.write(reply->readAll());
    file.close();
}

void Updater::updateVersionInfo()
{
    QFile file("version.xml");
    if(!file.open(QFile::WriteOnly))
        return;

    QDomDocument doc;
    QDomElement versionElement = doc.createElement("version");
    m_currectVersion.write(&versionElement);
    doc.appendChild(versionElement);
    file.write(doc.toString().toAscii());
    file.close();
}

void Updater::createUpdateItemsList()
{
    foreach(Update upd, m_updateList)
    {
        foreach(UpdateItem item, upd.itemList)
        {
            bool exists = false;
            for(int i = 0; i < m_updateItemList.count() && !exists; i++)
                if(m_updateItemList.at(i).path == item.path)
                    exists = true;

            if(!exists)
                m_updateItemList.append(item);
        }
    }
}

